Return to the table of contents

 


Functional Literacy

Form Follows Function

C++ was intended to be useful in writing large programs. Such programs are usually composed of many implementation files, as I mentioned in Chapter basics.htm.

In such a case, we must have some way of creating an executable program (sometimes abbreviated to just an executable) from a number of implementation files. We also need some way for code in one module to refer to code in another one. Similarly, we have to be able to specify where execution of our program should start; this is taken care of by the C++ rule that execution always starts at the block called main.

As we've already seen, the computer can't execute source code. Therefore, any implementation files we write have to be translated into object code; the result of such translation is an object code module. One other kind of module we're interested in is the library module, which contains the object code from several implementation files. Here's a list of these different kinds of modules, with a little more detail:

  1. An implementation file is a file that contains source code for a program. Almost every part of every program starts out as an implementation file.
  2. Compilation of an implementation file produces a file called an object code module (or object file), which contains object (machine) code.
  3. Several object code modules of a generally useful nature can be combined to make a file called a library module, usually abbreviated to library.

The idea of various types of modules led to the following discussion with Susan:

Susan: So an object file is like some kind of interface between your code and the binary numbers of the machine? I am confused.

Steve: Each implementation file is translated by the compiler into one object file. Then these object files will be combined along with some previously prepared library files, to make an executable program.

Susan: iostreams is a library? So are these like already written programs that you can refer to like a real library?

Steve: Yes.

Susan: The libraries contain code segments that are generalized, and the other modules contain code segments that are program specific?

Steve: Right, except that a library contains object code, not source code.

Susan: Where is the library? I am serious.

Steve: You can have more than one library, including ones written for more specialized purposes by different companies. The main library for the djgpp compiler is in a file called libgpp.a. However, the name of the main library file is dependent on which compiler you're using; in some cases, the library code is broken down into more than one file.

Susan: So what is an "implementation file"? Is it a set of code written by the programmer?

Steve: It's a file containing part of a program, in source code. Most significant programs consist of a number of modules, rather than one big one (file), partly because it's easier to find and edit one logical segment of a program than a whole bunch of code all in one big file.

Susan: Okay then, so a module is just a big logical segment? How is it delineated from the rest of the program? Is it named? How do you find it? Can you just tell by looking where a module starts and ends?

Steve: A module, in C++ terminology, is a file. Therefore, an implementation module is an implementation file, which contains program statements. It has a name, which is the name of the file.

Susan: So an implementation file is like a library, only as we have discussed it is more specific than a library; it is for the program that you are working on?

Steve: Right.

Susan: Where are these modules and how do they get there?

Steve: Wherever you (or whoever wrote the code) put them. In the case of your "weight-writing" program, the code you wrote is in an implementation file. That module is compiled to make an object code module, which is then combined with other object code modules that I have written, and library module(s) that come with the compiler, to make an executable file that can be run.

Susan: So then the implementation file is a "miniprogram" within a program that holds the source code to be later compiled?

Steve: It contains part of the source code of a program, which needs to be compiled and combined with other previously compiled code before it can be used. I think that's the same as what you're saying.

Actually, I've misused C++ terminology a little here in the interests of comprehensibility. The term block isn't quite correct as applied to main(); the correct term is function. Let's take a look at the difference between these two concepts, some other related definitions, and the objectives for this chapter.

Definitions

A block is a section of code that acts like one statement, as far as the language is concerned; that is, wherever a statement can occur, a block can be substituted, and it will be treated as one statement for the purposes of program organization.

A function is also a section of code, but its characteristics are different from those of a block. For one thing, you can't substitute a function for a statement; also, a function has a name, whereas blocks are anonymous. This name enables one function to start execution of another one.

A function call (or just "call" for short) causes execution to be transferred temporarily from the current function to the one named in the call.

A called function is a function that starts execution as a result of a function call.

A calling function is a function that suspends execution as a result of a function call.

A return statement is the mechanism used by a called function to return to the calling function, which picks up just where it left off.

Objectives of This Chapter

By the end of this chapter, you should

  1. Understand how and when to use functions to reduce the amount of code you have to write,
  2. Understand what software really is,
  3. Understand how your source code is turned into an executable program,
  4. Understand how storage is assigned to different types of variables,
  5. Understand how functions can call one another.

Functioning Normally

Susan had a question about this new notion of a function, as related to modules:

Susan: So a module has nothing to do with blocks and functions? If a function only "calls" another function, then how do you call a module?

Steve: The main idea of a module is to keep related functions together so they are easier to find and modify together. Some language features apply to modules rather than functions but they don't amount to much.

When we call a function, we usually have to provide it with input (for example, some values to be averaged) and it usually produces output which we use in further processing (for example, the average of the input values). Some functions, though, have only one or the other. For example, some pairs of functions consist of one storage function and one retrieval function; the first stores data for the second to retrieve later. In that case, the storage function may not give us anything back when we call it, and the retrieving function may not need any input from us.

To see how and why we might use a function, let's take a look at a program having some duplicated code (Figure nofunc1).

A sample program with duplicated code (code\nofunc.cc) (Figure nofunc1)

code/nofunc.cc

I'd like you to look particularly at this line,

AverageWeight = (FirstWeight + SecondWeight) / 2;

and this one,

AverageAge = (FirstAge + SecondAge) / 2;

These two lines are awfully similar; the only difference between them is that one of them averages two weights and the other averages two ages. While this particular example doesn't take too much code to duplicate, it may not be too difficult for you to imagine the inefficiency and nuisance of having to copy and edit many lines of code every time we want to do exactly the same thing with different data. Instead of copying the code and editing it to change the name of the variables, we can write a function that averages whatever data we give it. Figure functioncall is a picture of a function call.

A function call (Figure functioncall)

      +-------------------------------------------+                
      |                                           |                
   +--+----------------------------------------+  |                
   |  V                                        |  |                
   |  short Average(short First, short Second) |  |(3)                
   |                                           |  |                
   |  {                                        |  |                
   |                                           |  |                
   |    short Result;                          |  |                
   |                                           |  |                
   |    Result = (First + Second) / 2;         |  |                
   |                                           |  |                
 +-+--  return Result;                         |  |(4)                
 | |                                           |  |                
 | |  }                                        |  |                
 | +-------------------------------------------+  |                
 |                                                |                
 | +-------------------------------------------+  |                
 | | int main()                                |  |(1)                
 | |                                           |  |                
 | | {                                         |  |                
 | |                                           |  |                
 | |    short FirstAge;                        |  |                
 | |                                           |  |                
 | |    short SecondAge;                       |  |                
 | |                                           |  |                
 | |    short AvgAge;                          |  |                
 | |                                           |  |                
 | |    FirstAge = 5;                          |  |                
 | |                                           |  |                
 | |    SecondAge = 9;                         |  |                
 +-+----+                                      |  |                
   |    V   +----------------------------------+--+                 
   |    AvgAge = Average(FirstAge,SecondAge);  |   (2)                
   |                                           |                       
   |    cout << AvgAge << endl;                |   (5)                
   |                                           |                           
   |    return 0;                              |                           
   |                                           |                           
   | }                                         |                           
   +-------------------------------------------+           

The calling function (1) is main; the function call is at position (2). The called function is Average (3), and the return is at position (4); the returned value is stored in the variable AvgAge, as indicated by the assignment operator = in the statement AvgAge = Average(FirstAge,SecondAge); and the calling function, main, resumes execution at line (5).1 By the way, it's important to distinguish between returning a value from a function, which is optional, and returning control from a called function to the calling function, which always happens at the end of the called function (unless the program has terminated due to an error in the called function).

While we're on the subject of the calling function, in case you're wondering why we started the example at the beginning of main, it's because every C++ program starts executing at that point. When the main function calls another function, such as Average, then main is suspended until Average is finished. When Average finishes, main resumes where it left off.

This isn't limited to one "level" of calls. The same thing can happen if Average (for example) calls another function, let's say Funcx; Average will wait until Funcx returns before continuing. Then when Average finishes, it will return to main, which will take up where it left off.

This idea of calling and returning from functions led to the following discussion with Susan:

Susan: So if you wanted to be really mean you could get into someone's work in progress and stick a return somewhere in the middle of it and it would end the program right there? Now that I am thinking about it, I am sure you could do a whole lot worse than that. Of course, I would never do such a thing, but what I am saying is that whatever you are doing when the program gets to the return statement, then it is the end? Next stop, C:\?

Steve: If you're in the main program, then a return statement means "back to the C:\ prompt". If you're in a function other than main, it means "back to the function that called this function". In the case of a function that returns a value, the expression in the return statement tells the compiler what value to use in place of the function call. For example, the statement AvgAge = Average(i,j); sets AvgAge to the result in the return statement of the function Average. As you can see by looking at that function, the returned value is the average of the two input values, so that is the value that AvgAge is set to by this statement.

Susan: OK, but what about the return 0; at the end of the main program? Why should it be 0?

Steve: The return statement in the main program can return a different value if you wish. However, the custom is to return 0 from the main program to mean "everything went well" and some value greater than 0 to mean "there's a problem". This isn't entirely arbitrary. A batch file can test that return value and use it to alter the flow of execution of commands in the batch file.

Susan: OK, let's see if I have this right: The return statement has to match the main statement. This is so confusing. Look, when you say "The value that is returned, 0, is an acceptable value of the type we declared in the line int main ()" -- since I see no 0 anywhere around int main () -- you are referring to the int. An int can have a value of 0, right?

Steve: Right, the 0 has to match the int. That's because a function can have a return type, just like the type of a variable. In this case, int is the type of the main function, and the value is filled in by the return statement.

Susan: OK, then all this is saying is that the value that is produced is the same type as that declared at the beginning of a program. Since we declared the type of main as an int, if the value produced were a letter or a picture of a cow, then you would get an error message?

Steve: Well, actually a letter (i.e., a char) would be acceptable as an int, due to rules left over from C. Otherwise, you're exactly correct.

Susan: Hey, where is the 0 coming from to be returned?

Steve: It's specified as a literal value in the return statement; you could put any legal int value in there instead, if you wanted to.

Susan: So the return value doesn't have to be a 0?

Steve: Right.

Susan: So 0 could be another int value, but it can't be a variable? Even I don't know what I am talking about now!

Steve: I think I've confused you unnecessarily. You can certainly return a value that is specified as a variable, such as return i;. What I meant was that the 0 we're returning in this case is a constant, not a variable.

Susan: The picture helps with the calling confusion. But I don't understand why int main is the calling function if the calling function suspends execution. How can you initiate a function if it starts out suspended? But I am serious.

Steve: The main function starts execution as the first function in your program. Therefore, it isn't suspended unless and until it calls another function.

Above Average

I think it's time for a more detailed example of how we would use a function. Suppose we want to average sets of two numbers and we don't want to write the averaging code more than once. The Average function just illustrated provides this service; its input is the two numbers we want to average, and its output is the average. Figure funcaverage shows the code for the function Average without all the lines and arrows:

A function to average two values (Figure funcaverage)

short Average(short First, short Second)

{

short Result;

Result = (First + Second) / 2;

return Result;

}

Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this function in a running program.2 First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE func1, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type func1 to run the program.

To run it under the debugger, make sure you are in the code subdirectory, and then type "RHIDE func1". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger.3 As had become routine, I couldn't sneak this idea (of writing a function) past Susan without a discussion.

Susan: Where you say "and we don't want to write the averaging code more than once." Are you just saying if you didn't do the Average function thing then you would have to write this program twice? I mean for example would you have to write a program separately for weights and then another one from the beginning for ages?

Steve: We wouldn't have to write an entirely separate program; however, we would have to write the averaging code twice. One of the main purposes for writing a function is so that we don't have to repeat code.

To analyze this piece of code, let's start at the beginning. Every function starts with a function declaration, which tells the compiler some vital statistics of the function. The function declaration consists of three parts:

1. A return type;

2. The function's name;

3. An argument list.

In the case of our Average function, the function declaration is short Average(short First, short Second). The return type is short, the name of the function is Average, and the argument list is (short First, short Second). Let's take these one at a time.

Return to Sender

The first part of the function declaration is the return type, in this case short. This indicates that the function Average will provide a value of type short to the calling function when the Average function returns. Looking at the end of the function, you will see a statement that says return Result;. Checking back to the variable definition part of the function, we see that Result is indeed a short, so the value we're returning is of the correct type. If that were not the case, the compiler would tell us that we had a discrepancy between the declared return type of our function and the type actually returned in the code. This is another example where the compiler helps us out with static type checking, as mentioned in Chapter basics.htm; if we say we want to return a short and then return some other incompatible type such as a string, we've made a mistake.4 It's much easier for the compiler to catch this and warn us than it is for us to locate the error ourselves when the program doesn't work correctly.

Susan wanted to know more about the return type. Here's the conversation that ensued:

Susan: This return type thing -- it will have to be the same type of value as the output is?

Steve: For our purposes here, the answer is yes. As I've already mentioned, there are exceptions to this rule, but we won't need to worry about them.

Susan: Do you always use the word return when you write a function?

Steve: Yes, except that some functions have no return value. They don't have to have an explicit return statement, but can just "fall off the end" of the function, which acts like a return; statement. This is considered poor form, though; it's better to have a return statement.

The function name (in this case, Average) follows the same rules as a variable name. This is not a coincidence, because both function names and variable names are identifiers, which is a fancy word for "user defined names". The rules for constructing an identifier are pretty simple, as specified in the C++ Draft Standard: "An identifier is an arbitrarily long sequence of letters and digits. The first character is a letter; the underscore _ counts as a letter. Upper- and lower-case letters are different. All characters are significant." In other words:

  1. Your identifiers can be as long as you wish, and the compiler is required to distinguish between two identifiers, no matter how many identical characters they contain, as long as at least one character is different in the two names.5
  2. They can be made of any combination of letters and digits, as long as the first character is a letter. For historical reasons, the underscore character _ counts as a letter.6
  3. The upper and lower case versions of the same character aren't considered equal as far as names are concerned; that is, the variable xyz is a different variable from Xyz, while XYZ is yet another variable. Of course, you may get confused by having three variables with those names, but the compiler considers them all distinct.

By the way, the reason that the first character of an identifier can't be a digit is to make it easier for the compiler to figure out what's a number and what isn't. Another rule is that user defined names cannot conflict with names defined by the C++ language (keywords); some examples of keywords that we've already seen are if and short.

Finally, we have the argument list. In this case, it contains two arguments, a short called First, which holds the first number that our Average function uses to calculate its result; the second argument (also a short) is called Second, which of course is the other number needed to calculate the average. In other cases, there might be several entries in the argument list, each of which provides some information to the called function. But what exactly is an argument?

For the Sake of Argument

The question of what is an argument is more subtle than it may appear. An argument is a value that is supplied by a function (the calling function) that wishes to use the services of another function (the called function). For example, the calling function might be our main function, and the called function might be our Average function, while the arguments are two short values to be averaged. Arguments like the ones here are actually copies of values from the calling function; that is, the compiler will set the variable named in the argument list of the called function to the value supplied by the calling function. This process of making a copy of the calling function's argument is referred to as call by value, and the resulting copy is called a value argument.7 Figure onearg is an example of this argument passing mechanism at work with only one argument.

Argument passing with one argument (code\birthday.cc) (Figure onearg)

code/birthday.cc

In this program, main sets x to 46 and then calls Birthday with x as the argument. When Birthday starts, a new variable called age is created, and set to 46, because that's the value of x, the argument with which main called Birthday. Birthday adds one to its variable age, and then returns the new value of that variable to main. What number will be printed for the variable age by the line cout << "Your age was: " << x << endl;? Answer: 46, because the variable age in Birthday was a copy of the argument from main, not the actual variable x named in the call to Birthday. On the other hand, the value of y in the main program will be 47, because that is the return value from Birthday.

As you might have guessed, the notion of copying the argument when a function is called occasioned an intense conversation with Susan.

Susan: This is tough. I don't get it at all. Does this mean the value of the short named x will then be copied to another location in the function named Birthday?

Steve: Yes, the value in the short named x will be copied to another short called age before the execution of the first line in the function Birthday. This means that the original value of x in main won't be affected by anything that Birthday does.

Susan: Now for the really confusing part. I don't understand where you say "An argument like the one here (short age) is actually a copy of a value in the calling function". Now, I have read this over and over and nothing helped. I thought I understood it for a second or two and then I would lose it; finally I have decided that there is very little in this section that I do understand. Help.

Steve: When you write a function, the normal behavior of the compiler is to insert code at the beginning of the function to make a copy of the data that the calling function supplies. This copy of the data is what the called function actually refers to, not the original. Therefore, if you change the value of an argument, it doesn't do anything to the original data in the calling function.

If you (the programmer of the function) actually want to refer to the data in the calling function and not a copy of it, you can specify this when you write the function. There are cases in which this makes sense, and we'll see some of them in Chapter string.htm.

Susan: I don't understand why it is a copy of the calling function and not the called function.

Steve: It's not a copy of the calling function; it's a copy of the value from the calling function, for the use of the called function. In the sample program, main sets x to 46 and then calls Birthday with x as the argument. When Birthday starts, a new variable called age is created, and set to 46, because that's the value of x, the argument with which main called Birthday. Birthday adds 1 to its variable age, and returns the new value of age to main. What will be printed by the line "cout << x << endl;"? Answer: 46, because the variable age in Birthday was a copy of the value of the argument from main, not the actual variable (x) specified in the call to Birthday. Does this explanation clarify this point?

Susan: I still don't understand the program. It doesn't make any sense. If x = 46, then it will always be 46 no matter what is going on in the called function. So why call a function? You know what, I think my biggest problem is that I don't understand the argument list. I think that is where I am hung up on this.

Steve: The arguments to the function call (x, in the case of the function call Birthday(x)) are transferred to the value of the argument in the function itself (the short variable age, in the case of the function Birthday(short age)).

Susan: In that case, why bother putting an x there, why just not put 46? Would it not do the same thing in the called function, since it is already set to 46?

Steve: Yes, but what if you wanted to call this function from another place where the value was 97 rather than 46? The reason that the argument is a variable is so you can use whatever value you want.

Susan: If we called Birthday with the value 46, then the 46 would be 46++, right?

Steve: 46++ is a syntax error, because you can't change the value of a literal constant. Only a variable can be modified.

Susan: So if you want to state a literal value, do you always have to declare a variable first and then set a variable to that literal value?

Steve: No, sometimes you can use a literal value directly without storing it in a variable. For example,

cout << 15;

or

cout << "Hello, my name is Steve Heller";

What I was trying to say is that you can't change a literal value. Thus, 15++; is not legal because a literal value such as 15 represents itself, that is, the value 15. If you could write 15++;, what should it do? Change all occurrences of 15 to 16 in the program?

Susan: I get that. Now, how does age get initialized to the value of x?

Steve: The compiler does that when it starts the function, because you have declared in the function declaration that the argument to the function is called age, and you have called the function with an argument called x. So the compiler copies the value from x into age right before it starts executing the function.

Susan: Oh, OK. That makes sense, because maybe later on you want to call the same function again and change only a little part of it, but you still need the original to be the same, so you can just change the copy instead of the original. Is that the purpose?

Steve: The reason that the called function gets a copy of data rather than the original is so that the person writing the calling function knows that the original variable hasn't been changed by calling a function. This makes it easier to create programs by combining your own functions with functions that have already been written (such as in the library). Is that what you meant?

Susan: So is everything copied? I am getting confused again, are you going to talk a little more about copying in the book? Have I just not gotten there? Anyway, if you haven't mentioned this more, I think you should, it explains hidden stuff.

Steve: Don't worry, we're going to go into much more detail about how this works. It's a major topic in Chapter string.htm.

General Delivery

The same analysis that we have just applied to the Birthday function applies also to the Average function that we started out with; the arguments First and Second are copies of the values specified in the call to Average.

Now that we have accounted for the Average function's input and output, we can examine how it does its work. First, we have a variable definition for Result, which will hold the value we will return to the calling function; namely, the average of the two input values.

Then we calculate that average, with the statement Result = (First + Second) / 2;. Once the average has been calculated, we're ready to return it to the calling program, which is accomplished by the line return Result;. Finally, we reach the closing }, which tells the compiler that the function is done.

Using a Function

Now that we have seen how to write the Average function, let's see how to use it to solve our original problem. The program in Figure func1 uses our Average function twice, once to average two weights, and once to average two ages.

Using the Average function (code\func1.cc) (Figure func1)

code/func1.cc

As before, calling a function requires specifying its name and its argument(s) and doing something with the return value, if any. In this case, we call Average with the arguments FirstWeight and SecondWeight, and store the result in AverageWeight. This is accomplished via the line AverageWeight = Average(FirstWeight, SecondWeight);. Later, we call Average with the arguments FirstAge and SecondAge, and store the result in AverageAge. We do this via the line AverageAge = Average(FirstAge, SecondAge);. The value of writing a function to average two numbers wasn't obvious to Susan at first. After some discussion, however, she agreed that it was valuable. Here's the conversation that convinced her:

Susan: In general, I just don't understand why you even need to call the Average function in the first place; it looks like extra steps to me. It seems to me that all you need are your two input values, which end up just giving you the results right there for weight and age. I think that this is what bothers me the most. For example, when you get done with the set of weights, you should just have your results right then and there instead of calling the function of Average.

Steve: But what is the result you want? You want the average of the weights. Where is that calculated?

Susan: After you are done with that, then you already have written a set of ages so you can just use the result of that. It just seems like you are going in circles unnecessarily with this program. That is why I don't understand it.

Steve: Again, just because you have a set of ages doesn't mean that you have the average age; some code somewhere has to calculate that average.

Susan still had a lot of trouble with visualizing the way this function worked. However, running it in "trace" mode got her moving again, resulting in the following discussion:

Susan: Why does everything start out initialized to 0 except Result, which appears to hold an address in memory?

Steve: The values of uninitialized variables are not reliable. In this case, I'm getting a similar value of Result to the one you're getting; however, you cannot count on this. There's also no reason to think that the contents of Result are a memory address; they're just garbage until the variable is initialized.

Susan: Steve, I don't understand this; first you tell me that those numbers are garbage but represent addresses in memory and now you tell me that they are just garbage, but that they are not reliable. I don't understand, if they are uninitialized, how they ever could be reliable. This implies that at some time you could get an expected value even if they are uninitialized. They should always be garbage. So, when do those numbers represent memory addresses and when not?

Steve: Apparently I've confused you unnecessarily again. Here are the facts:

  1. The contents of an uninitialized variable are garbage.
  2. Since they are garbage, they represent nothing.
  3. Since they are garbage, they can have any value, which may or may not appear to have meaning. Regardless of appearances, the value of an uninitialized variable is meaningless.

Then our discussion returned to the topic of how the main program works:

Susan: Oh, OK, so AverageWeight = Average(FirstWeight, SecondWeight); is the part that starts the Average function running?

Steve: Right.

Susan: Then after averaging the weights, why does Result go to 0? It looks to me that Result has no value at these points and I don't understand why.

Steve: Because you're looking at the next call to Average, where its variable Result is uninitialized again. By default, variables are uninitialized whenever they are created, which occurs each time the function where they "live" is entered. The "old" Result from the first call to Average died when the first call to Average was finished, and the new Result that is created on the second call is uninitialized until we set it to some known value.

The next topic we discussed was how to create a new program and get it to run.

Susan: Now when you start out a new program; are all the new implementation files named with a .cc extension?

Steve: Yes.

Susan: So this code in Average is where the real averaging takes place, right? Is this the "Average command"? I thought Average meant to average, so what is the deal?

Steve: The deal is that something has to do the averaging; rather than writing the same code every time we need to average another set of two numbers, we put that code in one place (the Average function) and call it whenever we need its assistance. I've updated the text to explain this motivation for using functions.

Susan: OK, then this brings up another one of my questions. How come you write the Average function before the main function?

Steve: So that the main function knows how to call the Average function. There's another way to allow a function to call another one that doesn't come before it in the file, but I thought it was easier to show it this way at first.

Susan: If the main function is going to be executed first, then how come the Average function is written first? Does the compiler always look for main first?

Steve: Yes.

Susan: Yeah, but could the Average function then just be written after main instead of before it? Just to be there when it is needed, instead of before it is needed? Am I right that you still would not have to write it twice; it would still be there for the next time it is needed, right?

Steve: The Average function can be anywhere, even in a different module, but at the point that you try to use it, the compiler has to know about it; to be precise, you have to specify its return type and what arguments it takes. Otherwise, the program won't compile. On the other hand, the compiled version of the Average function doesn't have to be available until the executable is created; if it's not available at that point, you'll get an error saying that you have referenced an undefined function.

Susan: So does that mean you could put the Average function anywhere you want? Then could it or any "subfunction" be put anywhere you want because the main function would always be executed first? Or could you mess up the code if you put it in a really ridiculous place like inside an output or input statement. . . or could the compiler be able to ignore something like that and go about business as usual? I guess because of the brackets it should ignore such a thing but I am not sure. See, these are the things that we novices are obliged to ponder.

Steve: You can't start a function definition in the middle of another function. That's called a nested function and it's not allowed in C++. The rule can be stated approximately as "You can start a function definition anywhere except in the middle of another definition."

Susan: So then the "cue" for the Average function to begin is the word Average (weight) or (age), when the compiler sees that word it just begins that separate function to start its little calculation.

Steve: That's right, except that it needs two arguments, not just one.

Susan: And that function since it was named Average causes the averaging function to work. Is that how it goes?

Steve: If I understand your question, it's not the name that makes the Average function do the averaging, it's the code that adds up the two values and divides by 2. We could replace all the references to Average with the word Glorp and the compiler wouldn't care; however, a future programmer trying to read the program probably wouldn't be amused by that name.

Susan: Oh, so there is nothing magical about the word Average, I thought it might trigger a function of averaging. Well, that sounds reasonable; it's more for us humans than the computer. And then that brings up another question, along the same line of thinking. After the Average function has done its thing, how does the program go from return Result; to the next output statement that asks for the ages? What triggers that change? I am not seeing this in the code.

Steve: The return keyword tells the compiler to hand back control to the function that called the one where the return is, as indicated in Figure functioncall.

This discussion didn't slake her thirst for knowledge about how to write a program. Here is how we continued:

Susan: Can I mix shorts with strings using the headers that are already stated in the test program?

Steve: Mixing shorts with strings is a dubious proposition, sort of like asking a desk to make you lunch; could you be more specific about what you're trying to do?

Susan: What if you wanted to add a numerical value to your program such as test: You have to put in a short, right? So if you added a short, what else would you have to do to make it work? Or would you have to start over with another main function after the first part and then declare new variables? I tried that too, and the compiler did not like that either. Very inflexible it is. I will tell you after one more try what I am doing. This will keep you in suspense.

Steve: It depends on what you're trying to do with the short. It's usually best to have a specific problem in mind that you're trying to solve by writing a program. Then you can see how to use these facilities (shorts, strings, vectors, etc.) to solve your problem; if not, you can ask me how they would fit into the solution.

As for your second suggestion, you're not allowed to have more than one main function in a program, because the compiler wouldn't know which one to use as the starting address for the program.

Susan: I am not really trying to solve anything, I just want to have the user type in more info and that info is a number -- wait!! That is it, in that case it will be like an ASCII character and it doesn't need a short, right? That's right. I can still use a string. We are not crunching any numbers with this.

Steve: As long as you don't try to do any calculations, you can read the data into a string, even data that looks like a number; of course, that data entry method is pretty loose, since if the user types "abc" as an age, the program will accept it.

Susan: Can you define a string without a word but with just a wildcard type of variable like when we use i in shorts? In other words, does it matter what we call a variable?

Steve: A variable is always a "wildcard", whether it's a short or a string. For example, a short variable always has a name, such as i, or index, or whatever makes sense to you, and a value, which is a number such as 14 or 99. A string variable also has a name, such as FirstName, or street, or whatever makes sense to you, and a value, which consists of characters rather than a number, such as "Susan" or "Wesley".

A Convincing Argument

As you can see, using a function isn't very difficult. We have to provide it with the material to work on (its input arguments) and can store its return value in a variable for further processing (or use it directly, if we wish). But there's a little more here than meets the eye. How does the variable FirstWeight, for example, get transformed into the variable First that we used when writing the function? This explanation requires us to to look at some more underlying software technology: To be precise, we're going to spend some time examining the infrastructure that makes computers usable for programmers. First, though, we have to consider a more general notion, that of a "virtual computer".

The Man behind the Curtain

Unlike many words in the vocabulary of computing, virtual has more or less retained its standard English definition: "That is so in essence or effect, although not formally or actually; admitting of being called by the name so far as the effect or result is concerned."8 In other words, a virtual computer would be something that acts just like a computer, but really isn't one. Who would want such a thing?

Apparently everyone, since virtual computer is just another name for what we have been calling software. This may seem a rash statement, but it really isn't. One of the most important mathematical discoveries (inventions?) of the twentieth century was Alan Turing's demonstration that it was possible to create a fairly simple computing device (called a Turing machine for some reason) that could imitate any other computing device. This machine works in the following way: You provide it with a description of the other computer you want it to imitate, and it follows those directions. Suppose we want a computer that calculates only trigonometric functions. Then we could theoretically write a set of instructions as to how such a computer would behave, feed it into a Turing machine, and have the Turing machine imitate the behavior of this theoretical "trigonometric computer".

This is undoubtedly interesting, but you may be wondering what it has to do with programming. Well, what do we do when we write a program? In the case of our pumpkin weighing program, we're describing the actions that would be taken by a hypothetical "pumpkin weighing computer". When we run the program, the real computer simulates these actions. In other words, we have created a virtual pumpkin weighing computer.

The same analysis applies to any program. A program can most fundamentally be defined as instructions to a "universal computer", telling it how to simulate the specialized computer you actually want to use. When the universal computer executes these instructions, it behaves exactly as the hypothetical specialized computer would behave.

Of course, real computers aren't really universal; they have limits in the amount of memory or disk space they contain and the speed of their execution. However, for problems that can be solved within those limits, they are truly universal computing devices that can be tailored to a particular problem by programming.

The Object of My Affections

Now let's take a look at one of these areas of software technology. We've already seen that the function of a compiler is to convert our human-readable C++ program into machine instructions that can be executed by the computer. As we've already seen, the compiler doesn't actually produce an executable program that can stand by itself; instead, it translates each implementation file into a machine language file called an object code module (or object file). This file contains the instructions that correspond to the source code statements you've written, but not the "infrastructure" needed to allow them to be executed. We'll see what that infrastructure does for us shortly.

The creation of an object file rather than a complete executable program isn't a universal characteristic of compilers, dictated by nature. In fact, one of the most popular compilers in the early history of the PC, the Turbo PascalTM compiler, did create an executable file directly from source code. This appears much simpler, so we have to ask why this approach has been abandoned with C++. As I've mentioned before, C++ was intended to be useful in writing large programs. Such programs can consist of hundreds or even thousands of modules (sections of source code) each containing hundreds or thousands of lines of code. Once all the modules are compiled, the object files resulting from the compilation are run through a program called the linker. The linker combines information from all of the object files, along with some previously prepared files called library modules (or libraries), to produce an executable program; this is called linking the program. One reason for this two-step approach is that we wouldn't want to have to recompile every module in a large program every time we made a change in one section; therefore, only those modules that have been affected are recompiled.9 When all of the affected modules have been recompiled, the program is relinked to produce an updated executable.

To make such a system work, it's necessary to set up conventions as to which parts will be executed first, where data needed by more than one module will be stored, and so on. Also, a lot of operations aren't supplied as part of the language itself but are very handy in writing programs, such as the I/O functions that we've already seen. These make up the infrastructure needed to execute C++ programs.

Figure makeexe is a picture of the process of turning your source code into an executable file.

Making an executable (Figure makeexe)

      +---------------+    +---------------+                 
      |    Source     |    |    Source     |                 
      |     Code      |    |     Code      |                 
      +------+--------+    +-------+-------+                 
             |                     |                       
             |                     |                             
             |                     |                
           +-+---------------------+-+                            
           |         Compiler        |                 
           +-+---------------------+-+                 
             |                     |                
      +------+-------+    +--------+------+                 
      |    Object    |    |    Object     |                    
      |     File     |    |     File      |                    
      +------+-------+    +--------+------+                    
             |                     |                
             |  +---------------+  |                
             |  |    Library    |  |                
             |  |     File      |  |                
             |  +-------+-------+  |                
             +------+   |    +-----+                            
                    |   |    |                      
                  +-+---+----+-+                                
                  |   Linker   |                                           
                  +-----+------+                                 
                        |                       
                  +-----+------+                 
                  | Executable |                
                  +------------+

Susan found this explanation and diagram to be helpful.

Susan: This is beginning to come into focus. So you write your source code, it has a middle man called a object file and that just passes the buck over to a linker, which gathers info the program may need from the libraries, and then the program is ready to be read by the machine. Close?

Steve: Very close; in fact, exactly correct except for some nomenclature that isn't very important at this point.

Operating Systematically

As is often the case in programming, this infrastructure is divided into several layers, the higher ones depending on the lower ones for more fundamental services. The lowest level of the infrastructure is supplied by the operating system, a program that deals with the actual hardware of your computer. By far the most common operating system for Intel CPUs, as this is written, is MS-DOS (which is also the basis for Windows 95), followed by OS/2 and Windows NT. All of these provide some of the same facilities; for example, you are accustomed to dealing with files and directories when using application programs such as word processors and spreadsheets. However, the disk drive in your computer doesn't know anything about files or directories. As we have seen in Chapter hardware.htm, all it can do is to store and retrieve fixed-size pieces of data called sectors, given an absolute address on the disk described by a platter, track number, and sector number. Files are a creation of the operating system, which keeps track of which parts of which files are stored where on the disk.10 A modern operating system provides many more facilities than just keeping track of file storage. For example, it arranges for code and data to be stored in separate areas of RAM with different access rights, so that code can't be accidentally overwritten by a runaway program; that is, one that writes outside the memory areas it is supposed to use. This is a valuable service, as errors of this kind are quite difficult to find and can cause havoc when they occur.

That's the good news. The bad news is that MS-DOS was created before the widespread availability of reasonably priced CPUs with memory protection facilities and therefore doesn't take advantage of these facilities, although they are present in Windows 3.1 and Windows 95, which run "on top of" MS-DOS. Running under plain MS-DOS, it's entirely possible for a runaway program to destroy anything else in memory. Theoretically, we should all be running "real" operating systems by the time you read this; so far, though, the rumors of the demise of MS-DOS have been greatly exaggerated.

This notion intrigued Susan. Here's how that conversation went:

Susan: What is a runaway program?

Steve: One that is writing in areas that it shouldn't, thus destroying data or programs outside its assigned memory areas.

Susan: How would an operating system actually separate code from data areas? Would it be a physical thing?

Steve: What makes this possible are certain hardware mechanisms built into all modern CPUs, so that certain areas of memory can be assigned to specific programs, for use in predefined ways. When these mechanisms are used, a program can't write (or read, in some cases) outside its assigned area. This prevents one program from interfering with another.

Using Your Library Card

The next level of the infrastructure is supplied by the aforementioned library modules, which contain standardized segments of code that can perform I/O, mathematical functions, and other commonly used operations. So far, we have used the iostreams library, which provided the keyboard input and screen output in our example programs. We've also relied implicitly on the "startup" library, which sets up the conditions necessary for any C++ program to execute properly.11 Susan wasn't going to let me get away with this nonsense about the startup library without an explanation:

Susan: I don't know what you are talking about when you say that we have also used a startup library. When did we do that? At startup? Well, is it something that you are actually using without knowing you are using it?

Steve: Yes. It initializes the I/O system, and generally makes the environment safe for C++ programs; they're more fragile than assembly language programs and have to have everything set up for them before they can venture out.

To understand the necessity for the startup library, we have to take a look at the way variables are assigned to memory locations. So far, we have just assumed that a particular variable had a certain address, but how is this address determined in the real world?

There are several possible ways for this to occur; the particular one employed for any given variable is determined by the variable's storage class. The simplest of these is the static storage class; variables of this class are assigned memory addresses in the executable program when the program is linked. The most common way to put a variable in the static storage class is to define it outside any function.12 Such a variable will be initialized only once before main starts executing. We can specify the initial value if we wish; if we don't specify it, a default value (0 for numeric variables) will be assigned. An example of such a definition would be writing the line short x = 3; outside any function; this would cause x to be set to 3 before main starts executing. We can change the value of such a variable whenever we wish, just as with any other variable. The distinction I'm making here is that a static variable is always initialized before main begins executing. As you will see, this seemingly obvious characteristic of static variables is not shared with variables of other storage classes.

This idea of assigning storage at link time led to the following discussion with Susan:

Susan: If you declare a variable with only one value then it isn't a variable anymore, is it?

Steve: A static variable can have its value changed, so it's a genuine variable. I was saying that it's possible to specify what its initial value should be before main starts executing.

Susan: Are you trying to say where in memory this variable is to be stored? Isn't the compiler supposed to worry about that?

Steve: I'm not specifying a location, but rather an attribute of the variable. A static variable behaves differently from the "normal" variables that we've seen up till now. One difference is that a static variable is always initialized to a known value before main starts executing.

Automatic Pilot

The notion of storage classes is essential to the solution of another mystery. You may recall that I mentioned some time ago that C++ doesn't provide automatic initialization of all variables because that facility would make a program bigger and slower. I'll admit that the truth of this isn't intuitively obvious to the casual observer; after all, a variable (or more exactly, the storage location it occupies) has to have some value, so why not something reasonable? As we have just seen, this is done for static variables. However, there is another storage class for which such a facility isn't quite as easy or inexpensive to implement;13 that's the auto (short for "automatic") storage class, which is the default class used for variables defined in functions. An auto variable is not initialized until the function in which it is defined starts execution and even then has no known value until we specifically assign a value to it.14 This notion led to a fair amount of discussion between Susan and me.

Susan: Then so far I know about two types of variables, static and auto, is this correct?

Steve: Right, those are the two "storage classes" that we've used so far.

Susan: The auto variables are made up of garbage and the static variables are made up of something understandable, right?

Steve: The auto variables are uninitialized by default, and the static variables are initialized to 0 (if they're numeric, at least).

Susan: When you say that the auto class is used for functions by default does this mean you don't use static ones ever?

Steve: Default means "what you get if you don't specify otherwise". For example, these days, if you buy almost any type of car and don't specify that you want a manual transmission, you will get an automatic.

Susan: So, since we have used auto variables up to this point, then I am confused when we initialize them to a value. If we do, would that not make them static?

Steve: This is a difficult topic, so it's not surprising that you're having trouble. I didn't realize quite how difficult until I tried to answer your question and ended up with the essay found under the heading "Static Cling".

Susan: How do you know what the address will be to assign to a variable? OK, I mean this makes it sound like you, the programmer, know exactly which address in memory will be used for the variable and you assign it to that location.

Steve: You don't have to know the address; however, you do have to know that the address is fixed at link time. That's what makes it possible to initialize a static variable before main starts (if outside all functions) or just once when the function starts (if inside a function). On the other hand, auto variables can't be initialized until their functions start, because their addresses can change between executions of the function.

Susan: I am having a hard time trying to figure out what you mean by using static variables outside functions. If most of what we have been doing up to this point has been outside a function then why did you say that every variable we have used up to this point is auto? Or, I have meant to ask you this, is main really a function even if you don't have anything to call? For example, in the first pumpkin weighing program, we didn't have to call another function but I have been wondering if main is really a function that just has nothing to call? So in that case, the variables used in main would be auto?

Steve: You are correct that main is a function. To be precise, it's the first function executed in any C++ program. If it calls other functions, that's fine, but it doesn't have to. As with any other function, the variables used in main are auto by default; in the case of the pumpkin weighing program, since we didn't make any of them static, they're all auto in fact.

So far, all of our variables have been auto, and in most programs the vast majority of all variables are of this class.15 Why should we use these variables when static ones have that handy initialization feature built in?

The first clue to this mystery is in the name auto. When we define a variable of the auto class, its address is assigned automatically when its function is entered; the address is valid for the duration of that function.16 Since the address of the variable isn't known until its function is entered, it can't be initialized until then, unlike the case with static variables. Therefore, every function would have to start with some extra code to initialize every auto variable, which would make the program both slower and larger. Since Bjarne Stroustrup's design goals required that a C++ program should have the same run-time performance as a C program and as little space overhead as possible, such a feature was unacceptable. Luckily, forgetting to initialize an auto variable is something that can be detected at compile time, so it's possible for the compiler to warn us if we make this error. In general, it's a good idea to tell the compiler to warn you about dubious practices. Although not all of them may be real errors, some will be, and this is by far the fastest and best way to find them.17 Now we've seen why auto variables aren't initialized by default: Their addresses aren't known until entering the function in which they're defined. But that doesn't explain the advantage of assigning the addresses then. Wouldn't it be simpler (and faster) to assign them all during the linking process, as is done with static variables?

Stacking the Deck

To understand why auto variables aren't assigned addresses during the linking process, we have to look at the way functions relate to one another. In particular, it is very common for a statement in one function to call another function; this is called nesting functions, and can continue to any number of levels.

Susan explained this in her inimitable way.

Susan: Nesting of functions--does that mean a whole bunch of functions calling each other?

Steve: Yes.

Although functions can call one another, it is very unlikely that every function in a large program will be in the midst of execution at any given time. This means that reserving space in the executable program for all of the variables in all of the functions will make that executable considerably larger than it otherwise would be.

If we had only static variables, this wasteful situation would indeed occur. The alternative, of course, is to use auto variables, which as we have just noted are assigned storage at run time. But where is that storage assigned, if not in the executable program?

While all static variables are assigned storage when the executable program is linked, auto variables are instead stored in a data structure called a stack; the name is intended to suggest the notion of stacking clean plates on a spring-loaded holder such as you might see in a cafeteria. The last plate deposited on the stack of plates will be the first one to be removed when a customer needs a fresh plate. Back in the world of programming, a stack with one entry might look something like Figure stack1.18

A stack with one entry (Figure stack1)

       +--------------------+                 
TOP    |       1234         |                
       +--------------------+ 

If we add (or push) another value on to the stack, say 999, the result would look like Figure stack2.

A stack with two entries (Figure stack2)

       +--------------------+                 
TOP    |        999         |                
       +--------------------+                 
2nd    |       1234         |                
       +--------------------+ 

If we were to push one more item, this time with the value 1666, the result would look like Figure stack3.

A stack with three entries (Figure stack3)

       +--------------------+                 
TOP    |       1666         |                
       +--------------------+                 
2nd    |        999         |                
       +--------------------+                 
3rd    |       1234         |                
       +--------------------+ 

Now, if we retrieve (or pop) a value, we'll get the one on top; namely 1666. Then the stack will look like it did in Figure stack2. The next value to be popped off the stack will be the 999, leaving us with the situation in Figure stack1 again. If we continue for one more round, we'll get the value 1234, leaving us with an empty stack.

The reason that stacks are used to store auto variables is that the way items are pushed onto or popped off a stack exactly parallels what happens when one function calls another. Let's look at this stack idea again, but this time from the point of view of keeping track of where we are in one function when it calls another one, as well as allocating storage for auto variables.

Don't Call Me, I'll Call You

In Figure func1, there are two calls to the function Average: The first one is used to average two weights and the other to average two ages. One point I didn't stress was exactly how the Average function "knew" which call was which; that is, how did Average return to the right place after each time it was called? In principle, the answer is fairly simple: The calling function somehow notifies the called function of the address of the next instruction that should be executed after the called function is finished (the return address). There are several possible ways to solve this problem. The simplest solution is to store the return address at some standardized position in the code of the called function; at the end of the called function, that address is used to get back to the caller. While this used to be standard practice, it has a number of drawbacks that have relegated it to the history books. A major problem with this approach is that it requires changing data that are stored with the code of the called routine; as we've already seen, when running a program on a modern CPU under a modern operating system, code and data areas of memory are treated differently, and changing the contents of code areas at run time is not allowed.

Luckily, there is another convenient place to store return addresses: on the stack. This is such an important mechanism that all modern CPUs have a dedicated register, usually called the stack pointer, to make it easy and efficient to store and retrieve return addresses and other data that are of interest only during the execution of a function. In the case of the Intel CPUs, the stack pointer's name is esp.19 A machine instruction named call is designed to push the return address on the stack and jump to the beginning of the function being called.20 The call instruction isn't very complex in its operation, but before going into that explanation, you'll need some background information about how the CPU executes instructions.

How does the CPU "know" what instruction is the next to be executed? By using another dedicated register that we haven't discussed before, the program counter, which holds the address of the next instruction to be executed. Normally, this is the instruction physically following the one currently being executed; however, when we want to change the sequence of execution, as in an if statement or a function call, the program counter is loaded with the address of the instruction that logically follows the present one. Whatever instruction is at the address specified in the program counter is by definition the next instruction that will be executed; therefore, changing the address in the program counter to the address of any instruction causes that instruction to be the next one to be executed.

Here are the actual steps that the call instruction performs:

  1. It saves the contents of the program counter on the stack.
  2. Then it loads the program counter with the address of the first instruction of the called function.

What does this sequence of events achieve? Well, since the program counter always points to the next instruction to be executed, the address stored on the stack by the first step is the address of the next instruction after the call. Therefore, the last instruction in the called function can resume execution of the calling function by loading the program counter with the stored value on the stack. This will restart execution of the calling function at the next instruction after the call, which is exactly what we want to achieve.

The effect of the second step is to continue execution of the program with the first instruction of the called function; that's because the program counter is the register that specifies the address of the next instruction to be executed.

How It All Stacks Up

As we'll see, the actual way that a stack is implemented is a bit different than is suggested by the "stack of plates" analogy, although the effect is exactly the same. Rather than keeping the top of the stack where it is and moving the data (a slow operation), the data are left where they are and the address stored in the stack pointer is changed, which is a much faster operation. In other words, whatever address the stack pointer is pointing to is by definition the "top of the stack".21 For example, suppose that we start with an empty stack, with the stack pointer at 20001ffe, and ???? to indicate that we don't know the contents of that memory location. Thus, the stack will look like Figure average0.

An empty stack (Figure average0)

Address		  Contents		Meaning                
           +--------------------+                 
20001ffe   |       ????         |	 (none)                
           +--------------------+ 

Then, the user types in the two values "2" and "4" as the values of FirstWeight and SecondWeight, and the first call to Average occurs; let's suppose that call is at location 10001000.22 The actual sequence of events is something like this:23

  1. The address of the next instruction to be executed (say, 10001005) is pushed onto the stack, along with the values for the arguments First and Second, which are copies of the arguments FirstWeight and SecondWeight. In the process, the CPU will subtract eight (the size of one address added to the size of two shorts, in bytes) from the stack pointer (which is then 20001ff6) and store the return address at the address currently pointed to by the stack pointer. Thus, the stack looks like Figure average1.24

The stack immediately after the call to Average (Figure average1)

Address        Contents              Meaning                
            +--------------------+                 
20001ff2    |       ????         |   (none)                
            +--------------------+                 
20001ff4    |       ????         |   (none)                
            +--------------------+                 
20001ff6    |     10001005       |   return address in main                
            +--------------------+                 
20001ffa    |       0004         |   Second                
            +--------------------+                 
20001ffc    |       0002         |   First                
            +--------------------+                
20001ffe    |       ????         |   (none)                
            +--------------------+ 

  1. Execution starts in the Average function. However, before the code we write can be executed, we have to reserve space on the stack for the auto variable(s) defined in Average (other than the arguments First and Second, which have already been allocated); in this case, there is only one, namely Result. Since this variable is a short, it takes 2 bytes, so the stack pointer has to be reduced by 2. After this operation is completed, the stack will look like Figure average2.
  2. Wait a minute. What are those ???? doing at location 20001ff4? They represent an uninitialized memory location. We don't know what's in that location, because that depends on what it was used for previously, which could be almost anything. The C++ compiler uses stack-based addressing for auto variables, as well as copies of arguments passed in from the calling function; that is, the addresses of such variables are relative to the stack pointer, rather than being fixed addresses. In this case, the address of Result would be [esp], or the current value of the stack pointer; Second would be referred to in the object file as [esp+6] (i.e, 6 more than the current value of the stack pointer, to leave room for the return address and Result). Similarly, the address of First would be [esp+8], or 8 more than the current value of the stack pointer.25 Since the actual addresses occupied by these variables aren't known until the setup code at the beginning of the function is actually executed, there's no way to clear the variables out before then. That's why auto variables aren't automatically initialized.

The stack after auto variable allocation (Figure average2)

Address        Contents              Meaning                
            +--------------------+                 
20001ff2    |       ????         |   (none)                
            +--------------------+                 
20001ff4    |       ????         |   Result                
            +--------------------+                 
20001ff6    |     10001005       |   return address in main                
            +--------------------+                 
20001ffa    |       0004         |   Second                
            +--------------------+                 
20001ffc    |       0002         |   First                
            +--------------------+                
20001ffe    |       ????         |   (none)                
            +--------------------+ 

As you might have guessed, Susan and I went over this in gory detail. Here's the play by play.

Susan: Yes, I think this is what has confused me in the past about functions. I never fully understood how the mechanism worked as how one function calls another. I still don't. But I guess it is by the position of the next address in a stack?

Steve: The stack is used to pass arguments and get return values from functions, but its most important use is to keep track of the return address where the calling function is supposed to continue after the called function is done.

Susan: This is how I am visualizing the use of the stack pointer. In one of my other books it showed how the clock worked in the CPU and it seemed to cycle by pointing in different directions as to what was to happen next in a program. So it was sort of a pointer. Is this how this pointer works? So let me get this straight. All CPUs have some kind of stack pointer, but they are used only for calling functions? Exactly where is the instruction call? It sounds to me like it is in the hardware, and I am having a very difficult time understanding how a piece of hardware can have an instruction.

Steve: All of the instructions executed in a program are executed by the hardware. The call instruction, in particular, does two things:

  1. It saves the address of the next instruction (the contents of the program counter) on the stack.
  2. It changes the program counter to point to the first instruction of the called function.

The return instruction is used to return to the calling function. It does this by the following steps:

  1. It retrieves the saved value of the program counter from the top of the stack;
  2. It sets the program counter back to that value.

The result of this is that execution of the program continues with the next instruction in the calling function.

Susan: Are you saying that, rather than the pointer of a stack actually pointing to the top of the physical stack, wherever it points to by definition will be the top of the stack, even if it really doesn't look like it?

Steve: Exactly.

Susan: Now I see why we have to know what a short is. So then the pointer is pointing to 20001ff4 as the top of the stack even though it doesn't look like it?

Steve: Absolutely correct.

Scoped Out

Now let's look at another distinct way to categorize variables: the scope of a variable is the part of the program in which it can be accessed. Here, we are concerned with local scope and global scope. Variables with global scope are called global variables. These variables are defined outside any function and therefore by default can be accessed from any function.26 Global variables are always in the static storage class, as we have already seen.27 Variables with local scope are called local variables. These variables are defined in a function and are accessible only while that function is executing; they can be either static or auto and are auto by default.

Figure scopevssclass is a table of the allowable combinations of scope and storage class:

Scope vs. storage class (Figure scopevssclass)

                   Storage class                
                
                    static  auto                
                  +-------+-------+                 
          local   |   Y   |   Y   |                
 Scope            +-------+-------+                
          global  |   Y   |   N   |                
                  +-------+-------+

Susan had some comments on this topic.

Susan: On this scope stuff; I think you are going to have to help me understand exactly what is inside a function and what is outside a function. I am not too sure I know what the difference is.

Steve: All code is inside a function. However, some variables (global variables) are outside all functions and therefore shareable by all functions. Variables that are defined inside functions are called local, because they are available only to the code that's in that function.

Susan: I am validating this with you now. Please correct any misconceptions.

Steve: Correct.

Before we get deeper into the notion of scope, I think we should revisit the question of variable initialization in the light of the notion of global and local variables. This is a difficult topic, so it wouldn't be surprising if you don't find it obvious; Susan didn't. I wrote the next section to explain this topic to her.

Static Cling

What makes a variable static or auto is when its storage is assigned, and therefore when its address is known. In the case of a static variable, this happens at link time. In the case of an auto variable it happens when the function where it is defined is entered.28 This distinction affects initialization because it's impossible to initialize something until you know where it is. Therefore, an auto variable cannot be initialized until the function where it is defined is entered. This also means that it is impossible for an auto variable to retain its value from one execution of the function where it's defined to the next execution of that function, because the variable might be at a different location the next time.

These rules do not apply to static variables, because their addresses are known at link time and don't change thereafter. A variable defined outside all functions (a global variable) is automatically in the static storage class, because otherwise its address would never be assigned.29 Since its address is known at link time, the initialization of such a variable is performed before the start of main.

A static variable that is defined inside a function is different from one defined globally, in that it is not initialized until the function where it is defined is entered for the first time. However, its value is retained from one execution of its function to another, because its address is fixed rather than possibly varying from one call of the function to the next as can occur with an auto variable. For this property to be of use, the initialization of a static variable in a function must be performed only once; if it were performed on each entry to the function, the value from the previous execution would be lost. Therefore, that initialization is done only once, when the function is first entered.

Susan wanted some more explanation of what happens at link time, and the related question of why we would want to use static variables.

Susan: Will you tell me again what happens at link time? Let's see, I think it goes like this: The source code is translated into an object file and then the object file is linked to the hardware to make executable code. Is that how it goes?

Steve: Not quite. The object file is linked with library files containing predefined functions in compiled form.

Susan: Now, tell me again why you would want a variable to be static? What is the advantage to that? Does it just take up less room and be more efficient?

Steve: The advantage is that a static variable keeps its value from one function call to the next. For example, suppose you wanted to count the number of times that a function was called. You could use a static variable in the function, initialize it to 0, and add 1 to it every time the function was called. When the program was done, the value of that variable would be the number of times the function was called. Obviously, we couldn't use an auto variable to do that, as it would have to be initialized every time the function started or we'd have an uninitialized variable.

Susan: I can see how using a static variable would work but I don't see why an auto variable couldn't do the same thing. Well, I guess it would change each time the function would be used. Are you saying in this case that the variable has to be global?

Steve: Not exactly. Although we could use a global variable, we could also use a static local variable. Figures count1 through count6 are some sample programs to illustrate the situation.

Using an auto variable and initializing it (code\count1.cc) (Figure count1)

code/count1.cc

Using an auto variable and not initializing it (code\count2.cc) (Figure count2)

code/count2.cc

Using a local static variable and initializing it (code\count3.cc) (Figure count3)

code/count3.cc

Using a local static variable and not initializing it (code\count4.cc) (Figure count4)

code/count4.cc

Using a global variable and not initializing it (code\count6.cc) (Figure count6)

code/count6.cc

What will each of these programs do when run?

Susan: Now, let me think about this. Variables are: static or auto, global or local.

Local is for use only within functions. These variables are mostly auto, and will be auto by default, but they can be static; in the latter case, they will be initialized to 0.

Steve: Correct; a static numeric variable is initialized to 0 if no other provision is made by the programmer to initialize it.

Susan: Global variables are declared only outside functions. They are always allocated storage at link time, like static local variables.

Steve: Correct.

Susan: Variables with static allocation are fixed because they are initialized at link time and thus are done just once and never change. But they can be local or global.

Steve: The address of a statically allocated variable is set once and never changes; its value can change just like an auto variable can.

Susan: That's what I had mixed up. I was confusing addresses with values.

Steve: OK, as long as you're straightened out now.

Susan: An auto variable is assigned an address when the function where it is defined is entered. All auto variables are local.

Steve: Correct.

Susan: Now, here is where I am confused. What is the difference between at link time and when the function where it is defined is entered? Does at link time mean when you are done with your source code and you are making an executable?

Steve: Yes.

Susan: And does when the function where it is defined is entered mean when the program is already made into an executable and you are running the program?

Steve: Yes.

Susan: I am confused about what we mean by initialization. I am confusing declaring a value for a variable and the designation of an address for a variable. It almost seems as if we are using these two meanings for the same term. I always thought that initializing a variable meant just assigning a value to it.

Steve: Initializing a variable means assigning an initial value to it. In the case of an auto variable, this must be done every time the function where it is declared is entered, whereas with a static variable it is done once.

Susan: OK, this is what I imagined this to mean<?>. Then how come, in your figure of a stack, you have values assigned to the places where the variables are?

Steve: Those values are present only after the variables to which they correspond have been initialized. In Figure average2, for example, the contents of the address corresponding to Result are shown as ???, rather than as a valid value, whereas the values of First and Second are shown as initialized, because they have already been set to values equal to the input arguments provided by the caller.

Susan: Remember when I started the "tracing" program? It had random numbers in the places where numbers are supposed to go, and when I actually entered a value then those random numbers were replaced by that value. And is that why you put ??? there, because you know that something is there but you don't know exactly what? It is just whatever until you can put a real value into those slots.

Steve: Right.

Susan: So if you leave it alone by not initializing it, then it keeps the last value it had each time it goes through the loop and therefore the count goes up?

Steve: Yes, except that the initial value isn't reliable in that case. In the case of the example count programs, that value happened to be 0, but there's no reason to expect that in general.

Susan: I want you to know that it was not immediately apparent to me just what the code in the example programs was doing; it really does look kinda strange. Then I noticed that this code called a function named counter. Why? Couldn't this work without using a function call?

Steve: No, it wouldn't work without a function call, because the whole point is that when a function is called, the auto variables defined in that function have unknown values. How would I show that without a function call?

Susan: I see that. One more point, there was no 5, which I assumed you to mean to be a global initialized variable, so I made one myself by just copying 6 and assigning the value of 0 to count. So this is just about all this exercise did for me. . . . I still don't get it. Why? Because they all did the same thing except 1.The results for that were the following: 1 1 1 1 1 1 1 1 1 1. The results for the rest of the programs were all the same, being 1 2 3 4 5 6 7 8 9 10. So, if they all do the same thing, then what is the point? Now, what really makes me mad about this is why 1 has that result. This bothers me. Obviously it is not incrementing itself by 1 after the first increment, it is just staying at one. Oh, wait, okay, okay, maybe. . . how about this: If you initialize it to 0 then each time it comes up through the loop it is always 0 and then it will always add 1 to 0 and it has to do that 10 times.

Steve: Right. By the way, Figure count5 is the missing example.

Using a global variable and initializing it (code\count5.cc) (Figure count5)

code/count5.cc

Susan: Then on the initialized local static variable, why does it work? Because it is static, and because its address is one place and won't budge; that means its value can increment. Well, would that mean it isn't written over in its location every time the function is called so we can add a value to it each time through?

Steve: Right.

Susan: And then the uninitialized static one works for the same reason the auto uninitialized one works.

Steve: Not quite. A static local variable is always initialized to something, just like a global variable is. If you don't specify an initial value for a static local numeric variable, it will be initialized to 0 during the first execution of the function where it is defined.

Susan: Now as for global, this is hard. Let me guess. Do the global initialized and uninitialized work for the same reasons I said earlier?

Steve: The global variables are always initialized, whether you specify an initial value or not; if you don't specify one, it will be 0.

Susan: That's what you said about static numeric variables. Are they the same? Well, they have to be the same because only static variables can be global, right?

Steve: Correct. Global variables are always statically allocated.

Susan: So if you don't initialize a numeric variable then it can become any number unless it is a static numeric without explicit initialization and then it will be 0 by default?

Steve: Correct.

Susan: OK, let me see if I have this. All static really means is that the variable is put in an address of memory that can't be overwritten by another memory address but can be overwritten when we change the variable's value?

Steve: Right.

Susan: These are tricks, and you know I don't like tricks. If they are global numeric variables, whether explicitly initialized or not, they are static; therefore they will at least have a value of 0. In example 5 this is stated explicitly but not in example 6, so the variable also will take the value of 0 by default, therefore these two programs are effectively identical, just like 3 and 4. That is why examples 3, 4, 5, and 6 have the same results.

Steve: Well, obviously the trick didn't work; you crossed me up by getting the right answers anyway.

Let's pause here to look at a sample program that has examples of all the types of variables and initialization states we've just discussed. These are:30

  1. global, not explicitly initialized
  2. global, explicitly initialized
  3. auto, uninitialized
  4. auto, initialized
  5. local static, not explicitly initialized
  6. local static, explicitly initialized

Careful examination of the sample program, shown in Figure scopclas, will help you to visualize how and where each of these variables might be used.

Using variables of different scopes and storage classes (code\scopclas.cc) (Figure scopclas)

code/scopclas.cc

Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this program. First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE scopclas, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type scopclas to run the program.

To run it under the debugger, make sure you are in the code subdirectory, and then type "RHIDE scopclas". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger.

The results of using variables of different scopes and storage classes (code\scopclas.out) (Figure scopclas.out)

code/scopclas.out

The results shown should help to answer the question of when we would want to use a static variable rather than an auto variable: whenever we need a variable that keeps its value from one execution of a function to another. You may be wondering where that weird value for count3 came from. Since we never initialized it, we can't complain when its value is meaningless. Although the compiler can warn us about such problems in some cases, they are still a significant source of errors in C++ programs, so it's worthwhile doing some additional exercises to help drive the point home.

Exercises, First Set

  1. What will each of the programs in Figures inita through initf do when run?

Exercise 1a (code\inita.cc) (Figure inita)

code/inita.cc

Exercise 1b (code\initb.cc) (Figure initb)

code/initb.cc

Exercise 1c (code\initc.cc) (Figure initc)

code/initc.cc

Exercise 1d (code\initd.cc) (Figure initd)

code/initd.cc

Exercise 1e (code\inite.cc) (Figure inite)

code/inite.cc

Exercise 1f (code\initf.cc) (Figure initf)

code/initf.cc

Answers to exercises can be found at the end of the chapter.

Think Globally?

Now that, I hope, we've cleared up the question of when different types of variables are initialized, let's continue with the distinction between global and local variables. You may be surprised that a programmer would accept the limitation of allowing certain variables to be accessed only in certain functions. Surely it's more powerful to be able to access anything anywhere. Isn't it?

Let me tell you a little story about the "power" of global variables. Unlike the one about the funny odometers, this one is true.

A BASIC Difficulty

In the late 1970s, I worked for a (very) small software house that was developing a database program for the Radio Shack TRS-80 Model III computer. This computer was fairly powerful for the time; it had two 79K floppy disks and a maximum of 48K memory. The database program had to be able to find a subset of the few thousand records in the database in a minute or so. The speed of the floppy drive was the limiting factor. The only high-level language that was available was a BASIC interpreter clearly related by ancestry to QBASIC, the BASIC that comes with MS-DOS, but much more primitive; for example, variable names were limited to 2 characters.31 There was also an assembler, but even at that time I wasn't thrilled with the idea of writing a significant application program in assembly language. So we were stuck with BASIC.

Actually, that wasn't so bad. Even then, BASIC then had pretty good string manipulation functions, much better than the ones that come with C, and the file access functions, although primitive, weren't too hard to work with for the application in question. You could read or write a fixed number of bytes anywhere in a disk file, and since all of the records in a given database were in fact the same length, that was good enough for our purposes. However, there were a couple of (related) glaring flaws in the language: there were no named subroutines (analogous to functions in C++), and all variables were global.

Instead of names, subroutines were addressed by line number. In TRS-80 BASIC, each line had a number, and you could call a subroutine that started at line 1000 by writing "GOSUB 1000". At the end of the subroutine, a "RETURN" statement would cause control to return to the next statement after the GOSUB.

While this was functional in a moronic way, it had some serious drawbacks. First, of course, a number isn't as mnemonic as a name. Remembering that line 1000 is the beginning of the invoice printing routine, for example, isn't as easy as remembering the name PrintInvoice. In addition, if you "renumbered" the program to make room for inserting new lines between previously existing lines, the line numbers would change. The second drawback was that, as the example suggests, there was no way to pass arguments to a subroutine when it was called. Therefore, the only way for a subroutine to get input or produce output was by using and changing global variables. Yet another problem with this line-numbered subroutine facility was that you could call any line as a subroutine; no block structure such as we have in C++ was available to impose some order on the flow of control.

With such an arrangement, it was almost impossible to make a change anywhere in even a moderately large program without breaking some subroutine. One reason for this fragility was that a variable could be used or changed anywhere in the program; another was that it was impossible to identify subroutines except by adding comments to the program, which could be out of date. For both these reasons, almost any change could have effects throughout the program.

I Say "Live It, or Live with It"

After some time struggling with this problem, I decided to end it, once and for all, by adding named subroutines with arguments and local variables to the language. This made it possible to maintain the program, and we ended up selling several hundred copies of it over the course of a couple of years. Besides, fixing the language was fun.32 The moral? There's almost always a way around a limitation of a computer language, although it may not be worth the effort to find it. Luckily, with C++, adding functionality is a bit easier than patching BASIC in assembly language.

Nesting Instinct

After that (theoretically) instructive anecdote, it's time to get back to our regularly scheduled text, where we were examining the function of the stack in storing information needed during execution of a function. The next statement in our example program (Figure func1) is Result = (First + Second) / 2;. Since we've assumed that First is 2, and Second is 4, the value of Result will be (4+2)/2, or 3. After this statement is executed, the stack looks like Figure average3.

The stack after the initialization of Result (Figure average3)

Address        Contents              Meaning                
            +--------------------+                 
20001ff2    |       ????         |   (none)                
            +--------------------+                 
20001ff4    |       0003         |   Result                
            +--------------------+                 
20001ff6    |     10001005       |   return address in main                
            +--------------------+                 
20001ffa    |       0004         |   Second                
            +--------------------+                 
20001ffc    |       0002         |   First                
            +--------------------+                
20001ffe    |       ????         |   (none)                
            +--------------------+ 

Finally, at the end of the function, the stack pointer will be incremented to point to the stored return address. Then the return instruction will reload the program counter with the stored return address, which in this case is 10001005. Then the value of Result will be made available to the calling function and the stack pointer will be adjusted so the stack looks as it did before we called Average.

After the return, the stack will be empty, as we no longer need the arguments, the auto variable Result, or the return address from the Average function. Figure average4 shows what the stack looks like at this point.

The stack after exiting from Average (Figure average4)

Address        Contents              Meaning                
            +--------------------+                 
20001ff2    |       ????         |   (none)                
            +--------------------+                 
20001ff4    |       0003         |   (none)                
            +--------------------+                 
20001ff6    |     10001005       |   (none)                
            +--------------------+                 
20001ffa    |       0004         |   (none)                
            +--------------------+                 
20001ffc    |       0002         |   (none)                
            +--------------------+                
20001ffe    |       ????         |   (none)                
            +--------------------+ 

Do not be fooled by the casual statement "the stack is empty". That means only that the stack pointer (esp) is pointing to the same place it was when we started our excursion into the Average function; namely, 20001ffe. The values that were stored in the memory locations used by Average for its auto variables haven't been erased by changing the stack pointer. This illustrates one very good reason why we can't rely on the values of auto variables until they've been initialized; we don't know how the memory locations they occupy might have been used previously. The previous discussion of how arguments are copied into local variables when a function is called applies directly to our Average function; if we try to change the input arguments, we will change only the copies of those arguments, and the corresponding variables in the calling function won't be altered. That's perfectly acceptable here, since we don't want to change the values in the calling function; we just want to calculate their average and provide the result to the calling function. An argument that is handled this way is called a value argument, as its value is copied into a newly created variable in the called function, rather than allowing the called function access to the "real" argument in the calling function.33 One thing we haven't really discussed here is how the return value gets back to the caller. In the cases we've examined so far, using the compiler that accompanies this book, it's stored in a register (eax, to be precise), which is then available to the calling routine after we get back.34 This is a very easy and fast way to pass a return value back to the caller. However, it has a drawback: a register can only hold one value of 32 bits. Sometimes this is not enough, in which case another mechanism will have to be used; unfortunately, we won't get a chance to cover it in this book. In any event, it's time to go over the material we've covered in this chapter.

Review

First, we added the fundamental programming concept of the function. A function is a piece of code that can "stand alone"; it can be compiled separately from other functions, and provides some service that we can use via a mechanism known as a function call. The function that makes the call is known as the calling function, and the one it calls is known as the called function. Before we can call a function, we need to know what input values it needs and what it returns. This information is provided by a function declaration at the beginning of each function. This includes an argument list, which specifies input values that the called function uses (if any), and a return type, which specifies the type of the value that it produces when it's finished (if any). When we call a function, it executes until it reaches the end of its code or reaches a return statement, whichever comes first. When either of these events happens, the program continues execution in the calling function immediately after the place where the function call occurs. Ordinarily, as in our example, an argument to a function is actually a copy of the variable in the calling program, so that the called function can't modify the "real" value in the caller. Such an argument is called a value argument.35 We also saw that function and variable names can be of any length, consisting of upper or lower case characters (or both), digits, and the special character underscore (_). To make it easier for the compiler to distinguish numbers from variable names, the first character can't be a digit. Also, a variable name can't be the same as a keyword, or name defined by the language; examples of keywords we've seen so far include if, for, and short.

After finishing the construction of our Average function, we saw how to use it by making a function call. Then we launched into an examination of the way that values in the calling function are converted into arguments in the called function, which required a detour into the software infrastructure.

We started this excursion by looking at the linker, which is used to construct programs from a number of functions compiled into separate object files. Next, we explored the notion of storage class, which determines the working lifetime of a variable. The simplest storage class is static. Variables of this class, which includes all variables defined outside any function, have storage assigned to them by the linker and retain the same address during the lifetime of the program. On the other hand, auto (for "automatic") variables are always defined in a function and are assigned storage on the stack when that function starts execution. The stack is the data structure that stores function arguments and return addresses during the execution of a function; it's called that because it behaves like a spring-loaded stack of plates in a cafeteria, where the last one put on the top is the first one to be removed. Don't take this analogy too literally; most of the diagrams in this chapter show data being added and removed at the bottom rather than the top of the stack, but that doesn't affect its behavior.

Then we noted that each variable, in addition to a storage class, has a scope, which is the part of a program in which the variable can be accessed. At this point, the scopes that are important to us are local scope and global scope. As you might guess, a global variable can be referred to anywhere, while a local variable can be accessed only in the function where it is defined. Although it may seem limiting to use local variables rather than global ones, programs that rely on global variables are very difficult to maintain, as a change anywhere can affect the rest of the program. Programs that limit the scope of their variables, on the other hand, minimize the amount of code that can be affected by a change in one place. Because local variables are only usable while in the function where they are defined, they can be stored on the stack; therefore, they don't occupy memory during the entire lifetime of the program.

Of course, local variables take up room while they're being used, which means that the stack has to have enough storage to hold all of the local variables for the current function and all of the functions that haven't finished executing. That is, the stack has to have enough room for all of the variables in the current function, the function that called the current function, the one that called that one, and so on up to the main function. which is always the top-level function in a C++ program. Since the amount of memory that is allocated to the stack is not unlimited, it's possible to run out of space, in which case your program will stop working. This is called a stack overflow, by analogy with what happens if you put too many plates on the cafeteria plate stack: It falls over and makes a mess. When using the GNU compiler that comes with this book, it's unlikely that you'll ever run out of stack space unless you have a bug in your program. Other compilers aren't as generous in their space allotments, so the likelihood of a stack overflow is less remote. The solution to this problem, should it arise, is to use another kind of storage allocation called dynamic storage; we'll see an example of this mechanism in Chapter string.htm.

Now that we've gone through that review, it's time to do another exercise to drive home some points about scope and storage classes.

Exercises, Second Set

2. If the program in Figure calc1 is run, what will be displayed?

Exercise 2 (code\calc1.cc) (Figure calc1)

code/calc1.cc

Answers to exercises can be found at the end of the chapter.

Conclusion

We've covered a lot of material in this chapter, ranging from the anatomy of functions through a lot more information on what's going on "underneath the covers" of even a fairly simple C++ program. Next, we'll see how to write a realistic, although simplified, application program using some more advanced concepts in C++.

Answers to Exercises

  1. Here are the answers for each of the programs in Figures inita through initf, problems 1a-1f:
    1. code/inita.out
    2. code/initb.out
    3. code/initc.out
    4. code/initd.out
    5. code/inite.out
    6. code/initf.out

Why are these the way they are? Well, let's take them in order, except for b, which I'll take up last.

a. The reason for the results from a should be fairly obvious; since we set the variable count to 0 every time we enter the counter function, incrementing it always gives the answer 1.

As for c-f, they all produce the same answer; namely, the output value starts at 1 and increments by 1 each time. This is because the variable named count is statically allocated in each of these cases, which has two consequences: The initialization of the variable is done only once and it retains its value from one call of the counter function to the next. However, the reason for this behavior differs slightly in each of these cases, as follows:

c. In initc.cc, count is a static variable defined in the counter function, which is explicitly initialized to 0.

d. In initd.cc, count is a static variable defined in the counter function, which is not explicitly initialized. Statically allocated numeric variables are initialized to 0 if no other initial value is specified.36

e. In inite.cc, count is a global variable explicitly initialized to 0. Note that the keyword static is not used to specify that this variable is statically allocated; since globals are always statically allocated, the keyword static means something different when applied to a global variable or function. Even though we won't be using static for global variables or functions, it's possible that you will run into it in other programs, so it might be useful for you to have some idea of its meaning in those situations. An approximate translation of static for global functions or variables is that the function or variable is available for use only in the same file where it is defined, following the point of its definition.

f. In initf.cc, count is a global variable not explicitly initialized. As in initd.cc, this will work because the default value of a statically allocated numeric variable is 0.

Now let's see why b is different.

b. In initb.cc, count is a local uninitialized variable. Here's where we see the reason for the mess function, which apparently does nothing useful; that function is there solely to supply a value that will use the same memory location as the counter function will use to store its count variable after the mess function returns to the main program. In other words, in the DJGPP compiler that comes with this book, the variable xyz in the mess function happens to land in the same memory location that the variable count occupies in the counter function. Therefore, whatever value happened to be in xyz at the end of the mess function will appear as if by magic in count when counter starts. If we were to initialize count before we used it in counter, we would never see this leftover value, but since we just use count without initializing it, we get whatever value that memory location had left in it from before.

In case this point isn't yet apparent to you, I've drawn a set of pictures that might help clear it up. At the point in main where mess is about to be called, let's suppose that the stack is empty, with the stack pointer pointing to 20001ffe. The call instruction is at location 10001000, and is 5 bytes long, so the next instruction after the call starts at location 10001005. Before the call to mess occurs, the stack looks like Figure premess.37

The stack immediately before the call to mess (Figure premess)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       |  (none)                
            +-----------------+                 
20001ff4    |      ????       |  (none)                
            +-----------------+                 
20001ff6    |      ????       |  (none)                
            +-----------------+                 
20001ff8    |      ????       |  (none)                
            +-----------------+                 
20001ffa    |      ????       |  (none)                
            +-----------------+                 
20001ffe    |      ????       | (none)                
            +-----------------+ 

Then the call to mess occurs, which leaves the stack looking like Figure aftermess.

The stack immediately before the execution of the first instruction in mess FONT BOXFONT (Figure aftermess)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       | (none)                
            +-----------------+                 
20001ff4    |      ????       | (none)                
            +-----------------+                 
20001ff6    |      ????       | (none)                
            +-----------------+                 
20001ff8    |      ????       | (none)                
            +-----------------+                 
20001ffa    |    10001005     | return address in main                
            +-----------------+                
20001ffe    |      ????       | (none)                
            +-----------------+ 

Then mess declares a variable called xyz, which is an auto variable and therefore has to be stored on the stack. Since xyz is a short, it occupies 2 bytes on the stack, so the stack now looks like Figure messafterxyz.

The stack after mess has declared the auto variable xyz (Figure messafterxyz)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       | (none)                
            +-----------------+                 
20001ff4    |      ????       | (none)                
            +-----------------+                 
20001ff6    |      ????       | (none)                
            +-----------------+                 
20001ff8    |      ????       | xyz (uninitialized)                
            +-----------------+                 
20001ffa    |    10001005     | return address in main                
            +-----------------+                
20001ffe    |      ????       | (none)                
            +-----------------+ 

So far, so good. Now xyz is assigned the value 5, which leaves the stack looking like Figure afterxyzassign.

The stack after the auto variable xyz in mess is assigned the value 5 (Figure afterxyzassign)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       | (none)                
            +-----------------+                 
20001ff4    |      ????       | (none)                
            +-----------------+                 
20001ff6    |      ????       | (none)                
            +-----------------+                 
20001ff8    |      0005       | xyz                
            +-----------------+                 
20001ffa    |    10001005     | return address in main                
            +-----------------+                
20001ffe    |      ????       | (none)                
            +-----------------+ 

Then mess returns to main, so the stack is empty again. But here's the tricky part: to say "the stack is empty" merely means that the stack pointer has been reset back to 20001ffe. The data stored in locations 20001ff8-20001ffc have not been changed in any way.38 So, before counter is called, the situation looks like Figure beforecounter.

The stack before counter is called (Figure beforecounter)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       | (none)                
            +-----------------+                 
20001ff4    |      ????       | (none)                
            +-----------------+                 
20001ff6    |      ????       | (none)                
            +-----------------+                 
20001ff8    |      0005       | (none)                
            +-----------------+                 
20001ffa    |    10001005     | (none)                
            +-----------------+                
20001ffe    |      ????       | (none)                
            +-----------------+ 

When counter is called, let's assume the return address in main is now 10001013. After this is stored on the stack, we have the situation illustrated in Figure counterbeforefirst upon entry to counter.

The stack immediately before the execution of the first instruction in counter (Figure counterbeforefirst)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       | (none)                
            +-----------------+                 
20001ff4    |      ????       | (none)                
            +-----------------+                 
20001ff6    |      ????       | (none)                
            +-----------------+                 
20001ff8    |      0005       | (none)                
            +-----------------+                 
20001ffa    |    10001013     | return address in main                
            +-----------------+                
20001ffe    |      ????       | (none)                
            +-----------------+ 

The first thing that counter does is to allocate storage for its one auto variable, count, by subtracting 2 from the stack pointer. After this is done, the situation is as illustrated in Figure counterentry.

The stack when counter is entered (Figure counterentry)

Address        Contents          Meaning                
            +-----------------+                 
20001ff2    |      ????       | (none)                
            +-----------------+                 
20001ff4    |      ????       | (none)                
            +-----------------+                 
20001ff6    |      ????       | (none)                
            +-----------------+                 
20001ff8    |      0005       | count (uninitialized)                
            +-----------------+                 
20001ffa    |    10001013     | return address in main                
            +-----------------+                
20001ffe    |      ????       | (none)                
            +-----------------+ 

That is, the variable count is assigned the storage location that previously held the value of xyz; this storage location (20001ff8) still has the value 5 left over from xyz. Of course, if we initialize count as we should, we'll never see that old value. However, this program doesn't initialize count; thus, count starts out with the leftover value 5 from xyz before counter increments count.

The moral of the story is "always initialize your auto variables before use"; otherwise, you'll get whatever junk happens to be lying around in memory at the location where they are assigned when the function starts.

As you might guess, this set of problems led to a discussion with Susan.

Susan: Steve, what I don't get is what does the function called mess (aptly named) do? See, since I am still sketchy on function calls this really bothers me. Am I to assume that the mess function call comes first and then the counter function call comes second, and what does one have to do with the other?

Steve: As we've discussed before, all the lines in a given function (like main) are executed in sequential order from top to bottom, unless you use a flow control construct like if, while, or for, to change that order.

Susan: If it weren't for problem 1b, I would think that you were playing a trick on me and that this call was just a distraction and not utilized at all but I see it does do something, but what I am not sure.

Steve: You're correct that it does do something. One of the rules in figuring out a program is that everything does something; the question is often exactly what it is doing. Once you have that figured out, the rest is often pretty easy to determine.

2. If you got this one right, congratulations! It's just filled with tricks, but they're all things that you might run into in a real (poorly written) program. Here's the answer:

The value of j in Calc is: 0

The value of j in main is: 12

The value of j in Calc is: 5

The value of j in main is: 23

The value of j in Calc is: 16

The value of j in main is: 40

Let's see how this came about. The first question is why there are only three values displayed by each output statement. The for loop that calls the Calc routine and displays the results should execute 5 times, shouldn't it?

This is the first trick. Since i is a global variable, the statement i ++; in the Calc function affects its value. Therefore, i starts out at 0 in the main function, as usual, but when the Calc function is called, i is incremented to 1. So the next time the modification expression i ++ in the for statement is executed, i is already 1 and is changed to 2. Now the controlled block of the for statement is executed again, with i set to 2. Again, the call to Calc results in i being incremented an extra time, to 3, so the next execution of the for loop sets i to 4. The final call to Calc increments the value of i to 5, so the for loop terminates, having executed only three times rather than the five you would expect by looking at it. Now you can see why global variables are dangerous!

Now what about the values of j? Well, since the j in Calc is a static variable, it is initialized only once. Because it is a local static variable, that initialization is performed when Calc is called for the first time. So the first time Calc is called, j is set to 0. The arguments specified by main on the first call to Calc are 5 and 0; this means that, inside Calc, x and y have those values, respectively. Then the new value of j is calculated by the statement j = x + y + j;, or 5 in total. The return j; statement specifies this as the return value of Calc; this value is then added to 7 as specified by the assignment statement j = Calc(i + 5, i * 2) + 7; in main. That explains why the output statement in main displays the value of j as 12 the first time.

It's very important to note that the variable j in main is completely unrelated to the variable j in Calc. Since they are local variables, they have nothing in common but their names. There is no risk of confusion (at least on the compiler's part), since we can access a local variable only in the function in which it is defined. Therefore, when we refer to j in main, we mean the one defined there; and when we refer to j in Calc, we mean the one defined there.

Next, we call Calc again with the arguments 7 and 4. To compute these arguments from the expressions i + 5 and i * 2, you have to remember that i has been modified by Calc and is now 2, not 1 as we would expect normally. When we get to Calc, it displays the old value of j (5), left over from the previous execution of this function. This is because j is a local static variable; thus, the initialization statement static short j = 0; is executed only once, upon the first call to the function where it is defined. Once j has been set to a value in Calc, it will retain that value even in a subsequent call to Calc; this is quite unlike a normal auto variable, which has no known value at the beginning of execution of the function where it is defined. A new value of j is now calculated as 7 + 4 + 5, or 16, and returned to main.

On return from Calc, the value of j in main is 23, as set by the assignment statement j = Calc(i + 5, i * 2) + 7;. We also don't want to forget that i is now 3, having been changed in Calc.

Exactly the same steps occur for the last pass through the for loop: we call Calc with the new values of i + 5 and i * 2, which are 9 and 8, respectively, since i has been incremented to 4 by the for statement's modification expression i ++. Then Calc displays the old value of j, which is 16, and calculates the new value, which is 33. This is added to the literal value 7 and stored in j in main, resulting in the value 40, which is then displayed by the output statement.

Footnotes

  1. If you provide no return statement, then the called function will just return to the calling function when it gets to its closing }. However, this is not legal for a function that is defined to return a value. This of course leads to the question of why we'd call a function that doesn't return a value. One possibility is that the function exists only to produce output on the screen, rather than to return any results. The actions that a function performs other than returning a value are called side effects.
  2. If you want to see the whole program that this function is used in, it's in Figure func1.
  3. Please don't be confused by the seemingly random values that all of the variables start out with when tracing the program. These are just the garbage values that happen to be lying around in memory where those variables reside; as we've already seen, variables that haven't yet been assigned a value are called uninitialized variables. The variables in this program are all initialized before they are used, but the tracing program starts to display them before the initializing statements have been executed; therefore, they appear uninitialized at the beginning of the trace. We'll see later in this chapter exactly why we have to initialize all of our variables before we use them.
  4. What do I mean by an incompatible type? C++ has rules that, for example, allow us to return a char variable where a short (or an int) is expected; the compiler will convert the char into either of those types for us automatically. This is convenient sometimes, but it reduces the chances of catching an error of this kind, and therefore is less safe than it could be. This practice is a legacy from C, which means that it can't be changed for practical reasons, even though it is less than desirable theoretically.
  5. You don't have to worry about wasting space in your program by using long identifiers. They go away when your program is compiled and are replaced by addresses of the variables or functions to which they refer.
  6. You should avoid starting your variable or function names with an underscore, as such names are "reserved" for use by compiler writers and other language implementers.
  7. This discussion might make you wonder whether there's another type of argument besides a value argument. There is, and we'll find out about it in Chapter string.htm.
  8. Oxford English Dictionary, first current definition (4).
  9. The alert reader may wonder why I said "those modules that have been affected" rather than "those modules that have been changed". The reason is that even if we don't change a particular module, we will have to recompile it if a header file that it uses is changed. This can be a serious maintenance problem in large systems; there are special programming methods used to alleviate it, which are outside the scope of this book.
  10. You might say that files are "virtual"; that is, they're a figment of the operating system's imagination. Nonetheless, they are quite useful. This reminds me of the story about the man who went to a doctor, complaining that his brother had thought he was a hen for many years. The doctor asked why the family hadn't tried to help the brother before, and the man replied, "We needed the eggs".
  11. Two other libraries that we've used are the string and vector libraries.
  12. Another way to make a variable static is to state explicitly that the variable is static. However, this only works for variables defined inside functions. For the time being, we'll restrict the discussion to statically allocated variables defined outside any function.
  13. "Inexpensive", in programming parlance, means "not using up much time and/or space".
  14. I'm oversimplifying a bit here. Variables can actually be declared inside any block, not just any function. An auto variable that is declared inside a block is born when the block is entered and lives until that block is finished executing. A static variable that is declared inside a block is initialized when that block is entered for the first time. It retains its value from that point on unless it is explicitly changed, as with any other statically allocated variable.
  15. You may have noticed that we haven't defined any variables as auto. That's because, while auto and static are both storage classes and are equivalent in that regard, there's almost never any reason to explicitly mark a variable as auto; instead, any variables defined within a function and not marked as static are set to the default class, auto.
  16. This is why variables defined outside a function are static rather than auto; if they were auto, when would their addresses be assigned?
  17. There are also commercial tools that help locate errors of this type, as well as other errors that can be found by analyzing the source code for inconsistencies.
  18. The actual memory locations used to hold the items in the stack are just like any other locations in RAM. What makes them part of the stack is how they are used. Of course, as always, one memory location can hold only one item at a given time, so the locations used to hold entries on the stack cannot be simultaneously used for something else like machine instructions.
  19. That's the 32-bit stack pointer; as in the case of the other registers, there's a 16-bit stack pointer called sp, which consists of the 16 lower bits of the "real" stack pointer esp.
  20. That is, its name is call on Intel machines and many others; all modern CPUs have an equivalent instruction, although it may have a different name.
  21. Please note that the address that the stack occupies in these diagrams is arbitrary. The actual address where the stack is located in your program is determined by the linker and the operating system.
  22. As usual, all addresses are in hexadecimal.
  23. The details will vary with the compiler, but the principles are as illustrated here.
  24. The "Top of Stack" address, that is, the address where the stack pointer is pointing, will be bold. Also note that when we push items on the stack the stack pointer will move upward in the diagram. That's because lower addresses appear first in the diagram, and new items pushed onto the stack go at lower addresses. Anything in the diagram "above" the stack pointer (i.e., at a lower address than the stack pointer's current value) is not a meaningful value, as indicated in the "meaning" column.
  25. The actual mechanism used to refer to variables on the stack in a real compiler is likely to be different from this one, and indeed can vary among compilers. However, this implementation would work.
  26. Variables can be defined either inside a function (local variables) or outside a function (global variables); by contrast, code must always be inside a function.
  27. However, do not use the keyword static when defining global variables that you want to be statically allocated. That keyword, when applied to a global variable, means something else entirely.
  28. By the way, if you were worried about keeping track of the address of every variable, that's the compiler's problem, not yours. The important distinction between a static and an auto variable is when the address is assigned, not what the actual address is.
  29. Since all global variables are already in the static storage class, you don't have to (and shouldn't) use the keyword static when declaring a global variable. In another confusing legacy from C, the word static has an entirely different and unrelated meaning when used for a global variable.
  30. Remember, there aren't any global auto variables, because they would never be initialized.
  31. That is, two significant characters; you could have names as long as you wanted, but any two variables that had the same first two characters were actually the same variable.
  32. Unfortunately, the details of this adventure are both too technical and not very relevant to the task at hand. Also, I've forgotten exactly how I did it; after all, it was over 15 years ago.
  33. It's also possible to define a function that has access to an actual variable in the calling function; we'll see how and when to do that at the appropriate time.
  34. In case you're wondering how I know which register is used to pass back the return value, it's simple. I cheated by examining the compiled code with a utility program. Different compilers do this differently, but luckily you don't have to worry about this detail when writing C++ programs.
  35. As this might suggest, there is another type of argument, which we'll get to in Chapter inventor.htm.
  36. You can count on this, because it's part of the language definition, although it's nicer for the next programmer if you specify what you mean rather than leaving it to the compiler.
  37. As usual, the bold address indicates the current value of the stack pointer.
  38. This statement is true when running the DJGPP compiler on an Intel machine; it may not be true on other systems. However, that possibility only reinforces the point that you should not rely on such behavior, as it is outside the definition of the C++ language.

Return to the table of contents